package com.erik.crawler.service.ehcache;

import com.erik.crawler.model.IpInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class ProxyEhcache {
    @Value("${proxy.score.init}")
    private double initScore = 5;
    @Value("${proxy.score.min}")
    private double minScore = 0;
    @Value("${proxy.score.max}")
    private double maxScore = 10;

    /**
     * inner memory database
     */
    private Map<String, IpInfo> ipInfoMap = new ConcurrentHashMap<>();


    @CachePut(value = "ipInfo", key = "#ipInfo.ipStr")
    public IpInfo save(IpInfo ipInfo) {
        ipInfo.setScore(initScore);
        ipInfoMap.putIfAbsent(ipInfo.getIpStr(), ipInfo);
        return ipInfo;
    }

    @CacheEvict(value = "ipInfo")
    public void remove(String ipStr) {
        if (exists(ipStr)) {
            ipInfoMap.remove(ipStr);
        }
    }

    @Cacheable(value = "ipInfo", key = "#ipInfo.ipStr")
    public IpInfo findOne(String ipStr) {
        return ipInfoMap.get(ipStr);
    }

    /**
     * Determine whether the current agent already exists
     *
     * @param ipStr
     * @return
     */
    public boolean exists(String ipStr) {
        return ipInfoMap.containsKey(ipStr);
    }

    /**
     * proxy size
     *
     * @return
     */
    public long count() {
        return ipInfoMap.size();
    }

    /**
     * get all proxy
     *
     * @return
     */
    public Map<String, IpInfo> batchQuery() {
        return ipInfoMap;
    }

    /**
     * get high score proxy
     *
     * @return
     */
    public IpInfo randomGetHighAvailableProxy() {
        if (CollectionUtils.isEmpty(ipInfoMap)) {
            return null;
        }

        ConcurrentHashMap<String, IpInfo> sortedMap = ipInfoMap.entrySet().stream().sorted(new Comparator<Map.Entry<String, IpInfo>>() {
            @Override
            public int compare(Map.Entry<String, IpInfo> o1, Map.Entry<String, IpInfo> o2) {
                return Double.valueOf(o1.getValue().getScore()).compareTo(Double.valueOf(o2.getValue().getScore()));
            }
        }).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (e1, e2) -> e2, ConcurrentHashMap::new));

        Collection<IpInfo> ipInfos = sortedMap.values();
        if(ipInfos.size()>0){
            return (IpInfo) ipInfos.toArray()[0];//get high score
        }else{
            return null;
        }
    }

    /**
     * Set the available proxies to the maximum
     *
     * @param ipStr
     * @return
     */
    public boolean validOk(String ipStr) {
        IpInfo oldIpInfo = null;
        if (exists(ipStr)) {
            oldIpInfo = ipInfoMap.get(ipStr);
            IpInfo newIpInfo = oldIpInfo;
            newIpInfo.setScore(maxScore);
            ipInfoMap.computeIfPresent(ipStr, (k, v) -> newIpInfo);
            return true;
        }
        return false;
    }

    /**
     * Do - 5 or delete the score of the current agent,
     * and delete the initial score without passing the first verification
     *
     * @param ipStr
     * @return
     */
    public void decrementOrRemove(String ipStr) {
        if (exists(ipStr)) {
            IpInfo oldInfo = this.ipInfoMap.get(ipStr);
            Double score = oldInfo.getScore();
            if (score != null && score - 5 > minScore) {
                IpInfo newIpInfo = oldInfo;
                newIpInfo.setScore(score - 5);
                ipInfoMap.computeIfPresent(ipStr, (k, v) -> newIpInfo);
            } else {
                this.ipInfoMap.remove(ipStr);
            }
        }
    }
}
